//
//  AlamofireExample.swift
//  SwiftCode
//
//  Created by yuhus on 2023/8/1.
//

import UIKit
import Alamofire

final class AlamofireExample: ListViewController {
  override func getItemCount() -> Int {
    data.count
  }
  
  override func getItem(_ indexPath: IndexPath) -> Item {
    data[indexPath.row]
  }
  
  private lazy var data: [Item] = [
    .init(title: "Session.default", description: "请求百度", action: requests ),
    .init(title: "Session", description: "HTTPHeaders.default", action: configuration ),
    .init(title: "Request", description: "Request-GET", action: requests ),
    .init(title: "Request", description: "Request-Progress", action: progress ),
    .init(title: "URLSessionTaskMetrics", description: "URLSessionTaskMetrics", action: metrics ),
    .init(title: "DataRequest", description: "DataRequest-validate", action: dataRequest ),
    .init(title: "RequestAdapter", description: "添加 Authorization 到 Heard", action: requestAdapter ),
    .init(title: "Multiple RequestInterceptor", description: "使用多个拦截器", action: requestInterceptors ),
    .init(title: "AuthenticationInterceptor", description: "身份验证拦截器", action: authenticationInterceptor ),
    .init(title: "EventMonitors", description: "日志记录", action: logging ),
    .init(title: "Making Requests", description: "自定义实现 Request", action: makeRequests ),
    .init(title: "NetworkReachability", description: "侦听蜂窝网络和 WiFi 网络的可达性变化。", action: networkReachability ),
  ]
}

/**
 public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
 delegate: SessionDelegate = SessionDelegate(),
 rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"),
 startRequestsImmediately: Bool = true,
 requestQueue: DispatchQueue? = nil,
 serializationQueue: DispatchQueue? = nil,
 interceptor: RequestInterceptor? = nil,
 serverTrustManager: ServerTrustManager? = nil,
 redirectHandler: RedirectHandler? = nil,
 cachedResponseHandler: CachedResponseHandler? = nil,
 eventMonitors: [EventMonitor] = [])
 */

/// 要自定义底层 URLSession 的行为，可以提供自定义的 URLSessionConfiguration 实例。
///
///  建议从 URLSessionConfiguration.af.default 实例开始，因为它添加了 Alamofire 提供的默认 Accept-Encoding、Accept-Language 和 User-Agent 标头。
private func configuration() {
  // 不推荐在此设置 Authorization 和 Content-Type，它们应该在 Requests 的 header 中设置
  let configuratoin = URLSessionConfiguration.af.default
  print(HTTPHeaders.default)
  
  let session = Session(configuration: configuratoin)
  // Session 与苹果 URLSession 一样，初始化后给 URLSessionConfiguration 属性赋值不生效。
  configuratoin.allowsCellularAccess = false
  print(session.sessionConfiguration.allowsCellularAccess)
}

/// 用于任何磁盘访问，例如访问由 UploadRequests 上传的文件或由 DownloadRequests 下载的文件。
private func sessionDelegate() {
  _ = SessionDelegate(fileManager: .default)
}

/// 默认情况下，Session 一旦添加了至少一个响应处理程序，就会对请求调用resume()。 将 startRequestsImmediately 设置为 false 要求所有请求都手动调用resume()。
private func startRequestsImmediately() {
  _ = Session(startRequestsImmediately: false)
}

/// 默认情况下，Session 实例使用单个 DispatchQueue 来完成所有异步工作。 这包括 URLSession 代理的 OperationQueue 的底层队列，用于所有 URLRequest 创建、所有响应序列化工作以及所有内部 Session 和请 Request 状态突变。
///
/// 如果性能分析显示 URLRequest 创建或响应序列化存在特定瓶颈，则可以为每个工作区域的 Session 提供单独的 DispatchQueue。
private func dispatchQueue() {
  // 提供的自定义 rootQueue 必须是串行队列，但 requestQueue 和 SerializationQueue 可以是串行或并行队列。
  // 建议的默认值是串行队列，除非性能分析显示工作被延迟，在这种情况下，使队列并行可能有助于整体性能。
  let rootQueue = DispatchQueue(label: "com.app.session.rootQueue")
  let requestQueue = DispatchQueue(label: "com.app.session.requestQueue")
  let serializationQueue = DispatchQueue(label: "com.app.session.serializationQueue")
  
  _ = Session(
    rootQueue: rootQueue,
    requestQueue: requestQueue,
    serializationQueue: serializationQueue
  )
}

/// RequestInterceptor 协议（RequestAdapter & RequestRetrier）提供了重要且强大的请求适配和重试功能。 它可以应用于会话级别和请求级别。
private func requestInterceptor() {
  let policy = RetryPolicy()
  _ = Session(interceptor: policy)
}

/// ServerTrustManager 类封装了符合 ServerTrustEvaluating 类型的域和实例之间的映射，这提供了自定义会话对 TLS 安全性的处理的能力。 这包括使用证书和公钥固定以及证书吊销检查。
private func serverTrustManager() {
  // PinnedCertificatesTrustEvaluator 使用固定证书来验证服务器信任。
  let manager = ServerTrustManager(evaluators: ["httpbin.org": PinnedCertificatesTrustEvaluator()])
  _ = Session(serverTrustManager: manager)
}

/// RedirectHandler 协议自定义 HTTP 重定向响应的处理。 它可以应用于会话级别和请求级别。 Alamofire 包含符合 RedirectHandler 的 Redirector 类型，并提供对重定向的简单控制。
private func redirectHandler() {
  // follow 按照响应中定义的重定向进行操作。
  let redirector = Redirector(behavior: .follow)
  _ = Session(redirectHandler: redirector)
}

/// CachedResponseHandler 协议可自定义响应的缓存，并且可以应用于会话和请求级别。 Alamofire 包含符合 CachedResponseHandler 的 ResponseCacher 类型，并提供对响应缓存的简单控制。
private func cachedResponseHandler() {
  let cacher = ResponseCacher(behavior: .cache)
  _ = Session(cachedResponseHandler: cacher)
}

/// EventMonitor 协议提供了对 Alamofire 内部事件的强大洞察。 它可用于提供日志记录和其他基于事件的功能。 Session 在初始化时接受一组符合 EventMonitor 的实例。
private func eventMonitors() {
  let monitor = ClosureEventMonitor()
  monitor.requestDidCompleteTaskWithError = { (request, task, error) in
    debugPrint(request)
  }
  let session = Session(eventMonitors: [monitor])
  
  // 尽管使用应该很少，但 Session 提供了 withAllRequests 方法来对所有当前活动的请求进行操作。 这项工作是在会话的 rootQueue 上执行的，因此保持快速非常重要。 如果工作可能需要一些时间，则应使用创建一个单独的队列来处理请求集。
  session.withAllRequests { requests in
    requests.forEach { $0.suspend() }
  }
  
  // 此外，Session 提供了一种方便的方法来取消所有请求并在完成时调用完成处理程序。
  session.cancelAllRequests(completingOnQueue: .main) {
    print("Cancelled all requests.")
  }
}

/// 直接从 URLSession 初始化。
///
/// Alamofire 不支持配置为后台使用的 URLSession。 这将导致 Session 初始化时出现运行时错误。
/// 必须创建 SessionDelegate 实例并将其用作 URLSession 的委托，并传递给 Session 初始值设定项。
/// 自定义操作队列必须作为 URLSession 的 delegateQueue 传递。 该队列必须是串行队列，它必须有一个后备 DispatchQueue，并且该 DispatchQueue 必须作为其 rootQueue 传递给 Session。
private func instanceFromURLSession() {
  let rootQueue = DispatchQueue(label: "org.alamofire.customQueue")
  let queue = OperationQueue()
  queue.maxConcurrentOperationCount = 1
  queue.underlyingQueue = rootQueue
  let delegate = SessionDelegate()
  let configuration = URLSessionConfiguration.af.default
  let urlSession = URLSession(configuration: configuration, delegate: delegate, delegateQueue: queue)
  _ = Session(session: urlSession, delegate: delegate, rootQueue: rootQueue)
}

// MARK: - Requests

/**
 Alamofire 执行的每个请求都由特定的类 DataRequest、UploadRequest 和 DownloadRequest 封装。 这些类中的每一个都封装了每种请求类型所特有的功能，但 DataRequest 和 DownloadRequest 继承自公共超类 Request（UploadRequest 继承自 DataRequest）。 请求实例永远不会直接创建，而是通过各种请求方法之一从 Session 实例中发出。
 
 The Request Pipeline
 
 一旦使用其初始参数或 URLRequestConvertible 值创建了 Request 子类，它就会通过构成 Alamofire 请求管道的一系列步骤进行传递。 对于成功的请求，这些包括：
 
 1. 初始参数（如 HTTP 方法、标头和参数）封装到内部 URLRequestConvertible 值中。 如果直接传递 URLRequestConvertible 值，则该值将保持不变。
 2. 通过调用 URLRequestConvertible 的 asURLRequest()，创建第一个 URLRequest 值。 该值被传递给 Request 并存储在 requests 中。 如果 URLRequestConvertible 值是根据传递给 Session 方法的参数创建的，则在创建 URLRequest 时将调用提供的 RequestModifier。
 3. 任何 Session 或 Request，如果有 RequestAdapters 或 RequestInterceptors，它们会被之前创建的 URLRequest 调用。 然后，调整后的 URLRequest 被传递到 Request 并存储在 requests 中。
 4. Session 根据 URLRequest 调用 Request 创建 URLSessionTask 执行网络请求。
 5. 一旦 URLSessionTask 完成并收集了 URLSessionTaskMetrics，请求就会执行其验证器。
 6. Request 执行任何已附加的响应处理程序，例如 responseDecodable。
 
 在这些步骤中的任何一个，都可以通过创建或接收的错误值来指示失败，然后将该错误值传递给关联的请求。 例如，除了步骤 1 和 4 之外，上述所有步骤都可能创建一个错误，然后将其传递给响应处理程序或可用于重试。 以下是整个请求管道中可能或不能失败的一些示例。
 1. 参数封装不会失败。
 2. 调用 asURLRequest() 时，任何 URLRequestConvertible 值都可能会产生错误。 这允许对各种 URLRequest 属性进行初始验证或参数编码失败。
 3. RequestAdapters 在适应过程中可能会失败，可能是由于缺少授权令牌。
 4. URLSessionTask 创建不会失败。
 5. 由于多种原因，URLSessionTasks 可能会在完成时出现错误，包括网络可用性和取消。 这些错误值被传回请求。
 6. 响应处理程序可能会产生任何错误，通常是由于无效响应或其他解析错误。
 
 一旦错误传递给 Request，Request 将尝试运行与会话或请求关联的任何 RequestRetriers。 如果任何 RequestRetriers 选择重试该请求，则整个管道将再次运行。 RequestRetriers 也可能产生错误，但不会触发重试。
 */

/// 所有请求类型都包含状态概念，指示请求生命周期中的主要事件。Request.State
///
/// `suspend()` 挂起或暂停请求及其网络流量。 处于此状态的请求可以恢复，但只有 `DownloadRequest` 可以继续传输数据。 其他请求将重新开始。
private func requests() {
  AF.request("https://www.baidu.com")
    .responseString { response in
      debugPrint(#line, response)
    }
}

/// 并非所有 Request 子类都能够准确报告其进度，或者可能有其他依赖项来执行此操作。
///
/// 对于下载进度，有一个要求：
///  - 服务器响应必须包含 Content-Length
private func progress() {
  AF.request("https://api.metaworld.fun/tt/v2/resource/get?resourceId=10086")
    .uploadProgress { progress in
      print(#line, progress)
    }
    .downloadProgress { progress in
      print(#line, progress)
    }
    .responseDecodable(of: DecodableType.self) { response in
      debugPrint(#line, response)
    }
}

private typealias DecodableType = MetaModel.ResponseData<MetaModel.TValue>

/// 每个请求还可以指定自己的 RedirectHandler，该 RedirectHandler 会覆盖会话提供的任何 RedirectHandler。
private func handleRedirect() {
  // 一个Request只能设置一个RedirectHandler。 尝试设置多个将导致运行时异常。
  let redirector = Redirector(behavior: .follow)
  AF.request("https://api.metaworld.fun/tt/v2/resource/get?resourceId=10086")
    .redirect(using: redirector)
    .responseDecodable(of: DecodableType.self) { response in
      debugPrint(response)
    }
}

private func customCaching() {
  // 一个Request只能设置一个CachedResponseHandler。 尝试设置多个将导致运行时异常。
  let cacher = ResponseCacher(behavior: .cache)
  AF.request("https://api.metaworld.fun/tt/v2/resource/get?resourceId=10086")
    .cacheResponse(using: cacher)
    .responseDecodable(of: DecodableType.self) { response in
      debugPrint(response)
    }
}

private func credentials() {
  // 该机制仅支持HTTP认证提示。 如果请求需要所有请求的 Authentication 标头，则应直接提供它，或者作为请求的一部分，或者通过 RequestInterceptor 提供。
  AF.request("https://api.metaworld.fun/tt/v2/resource/get?resourceId=10086")
    .authenticate(username: "user@example.domain", password: "password")
    .responseDecodable(of: DecodableType.self) { response in
      debugPrint(response)
    }
  
  let credential = URLCredential()
  AF.request("https://api.metaworld.fun/tt/v2/resource/get?resourceId=10086")
    .authenticate(with: credential)
    .responseDecodable(of: DecodableType.self) { response in
      debugPrint(response)
    }
}

/**
 Alamofire 在请求的整个生命周期中创建各种基础值。 其中大部分是内部实现细节，但 URLRequest 和 URLSessionTask 的创建是公开的，以允许与其他 API 直接交互。
 
 Request 发出的每个网络请求最终都封装在 URLRequest 值中，该值是根据传递到会话请求方法之一的各种参数创建的。 Request 将在其 requests 数组属性中保留这些 URLRequest 的副本。 这些值包括从传递的参数创建的初始 URLRequest 以及由 RequestInterceptors 创建的任何 URLRequest。 但是，该数组不包括代表请求发出的 URLSessionTasks 执行的 URLRequest。 为了检查这些值，tasks 属性可以访问请求执行的所有 URLSessionTasks。
 
 除了累积这些值之外，每个 Request 都有一个 onURLRequestCreation 方法，每当为该 Request 创建 URLRequest 时，该方法都会调用闭包。 此 URLRequest 是传递给 Session 的请求方法的初始参数以及任何 RequestInterceptor 所应用的更改的产物。 如果重试请求并且一次只能设置一个关闭，它将被多次调用。 在此闭包中无法修改 URLRequest 值； 如果您需要在发出 URLRequest 之前修改它们，请在将请求传递到 Alamofire 之前使用 RequestInterceptor 或使用 URLRequestConvertible 协议编写请求。
 */
private func onURLRequestCreation() {
  AF.request("https://api.metaworld.fun/tt/v2/resource/get?resourceId=10086")
    .onURLRequestCreation { request in
      print(request)
    }
    .responseDecodable(of: DecodableType.self) { response in
      debugPrint(response)
    }
}

/// 同 onURLRequestCreation
private func onURLSessionTaskCreation() {
  AF.request("https://api.metaworld.fun/tt/v2/resource/get?resourceId=10086")
    .onURLSessionTaskCreation { task in
      print(task)
    }
    .responseDecodable(of: DecodableType.self) { response in
      debugPrint(response)
    }
}

/// 一旦请求完成，每个请求都可以有一个可用的 HTTPURLResponse 值。 仅当请求未取消且网络请求未失败时，此值才可用。 此外，如果重试请求，则只有最后的响应可用。 中间响应可以从任务属性中的 URLSessionTasks 派生。
private func response() {
  
}

/// Alamofire 收集为请求执行的每个 URLSessionTask 的 URLSessionTaskMetrics 值。 这些值在metrics属性中可用，每个值对应于相同索引的任务中的URLSessionTask。
private func metrics() {
  AF.request("https://api.metaworld.fun/tt/v2/resource/get?resourceId=10086")
    .responseDecodable(of: DecodableType.self) { response in
      guard let metrics = response.metrics else { return }
      print(metrics)
    }
}

/// DataRequest 是 Request 的子类，它封装了一个 URLSessionDataTask，将服务器响应下载到存储在内存中的数据中。 因此，重要的是要认识到极大的下载可能会对系统性能产生不利影响。 对于这些类型的下载，建议使用 DownloadRequest 将数据保存到磁盘。
///
/// typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> Result<Void, Error>
private func dataRequest() {
  // 默认情况下，添加 validate() 可确保响应状态代码在 200..<300 范围内，并且响应的 Content-Type 与请求的 Accept 值匹配。 可以通过传递 Validation 闭包来进一步自定义验证：
  
  AF.request("https://api.metaworld.fun/tt/v2/resource/get?resourceId=10086")
    .validate(statusCode: 200..<300)
    .validate { request, response, data in
      // ...
      return .success(())
    }
    .responseDecodable(of: DecodableType.self) { response in
      print(response)
    }
}

/// DataStreamRequest 是 Request 的子类，它封装了 URLSessionDataTask 并随着时间的推移从 HTTP 连接流式传输数据。
///
/// typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse) -> Result<Void, Error>
private func dataStreamRequest() {
  AF.streamRequest("https://api.metaworld.fun/tt/v2/resource/get?resourceId=10086")
    .validate { request, response in
      // ...
      return .success(())
    }
}

/// UploadRequest 是 DataRequest 的子类，它封装了一个 URLSessionUploadTask，上传 Data 值、磁盘上的文件或 InputStream 到远程服务器。
private func uploadRequest() {
  AF.upload(Data(), to: "")
    .responseDecodable(of: DecodableType.self) { response in
      print(response)
    }
}

/// DownloadRequest 是 Request 的具体子类，它封装了 URLSessionDownloadTask，将响应数据下载到磁盘。
///
/// 除了 Request 提供的属性之外，DownloadRequests 还有一些属性。 其中包括 resumeData（取消下载请求时生成的数据，可用于稍后恢复下载）和 fileURL（下载完成后下载的文件可用的URL）。
private func downloadRequest() {
  // 除了支持 Request 提供的 cancel() 方法之外，DownloadRequest 还包括 cancel(havingResumeData shouldProduceResumeData: Bool)，如果可能的话，它可以在取消时选择性地填充 resumeData 属性，以及 cancel(byProducingResumeData finishHandler: @escaping (_ data: Data?) -> Void），它将生成的恢复数据提供给传递的闭包。
  AF.download("")
    .cancel { data in
      // ...
    }
}

// MARK: - Adapting and Retrying Requests with RequestInterceptor

/**
 Alamofire 的 RequestInterceptor 协议（由 RequestAdapter 和 RequestRetrier 协议组成）可实现强大的会话和请求功能。 其中包括身份验证系统，其中将公共标头添加到每个请求中，并在授权过期时重试请求。 此外，Alamofire 还包含内置的 RetryPolicy 类型，当请求因各种常见网络错误而失败时，可以轻松重试。
 */

/// 防止  Alamofire.AFError.sessionDeinitialized
private var customSession: Session?

/// RequestAdapter 协议允许会话执行的每个 URLRequest 在通过网络发出之前进行检查和修改。 适配器的一个非常常见的用途是向特定类型的身份验证后面的请求添加授权标头。
private func requestAdapter() {
  let token = TokenAdapter()
  let session = Session(interceptor: token)
  customSession = session
  
  session.request("https://www.baidu.com")
    .responseString { response in
      debugPrint(#line, response)
    }
}

private func requestInterceptors() {
  let adapter = TokenAdapter()
  let retrier = RetryPolicy()
  let interceptor = HeaderAdapter()
  
  let composite = Interceptor(interceptors: [adapter, retrier, interceptor])
  
  let session = Session(interceptor: composite)
  customSession = session
  
  session.request("https://www.baidu.com")
    .responseString { response in
      debugPrint(#line, response)
    }
}

/// AuthenticationInterceptor 类是一个 RequestInterceptor，旨在处理与身份验证请求相关的排队和线程复杂性。 它利用注入的 Authenticator 协议来管理匹配的 AuthenticationCredential 的生命周期。 以下是如何与 OAuthCredential 一起实现 OAuthAuthenticator 类的简单示例。
private func authenticationInterceptor() {
  Timber.d(#function)
  let adapter = HeaderAdapter()
  
  let authenticator = OAuthAuthenticator(Session(interceptor: adapter))
  let credential = OAuthCredential( accessToken: "accessToken", refreshToken: "")
  let interceptor = AuthenticationInterceptor(authenticator: authenticator, credential: credential, refreshWindow: .init(interval: 5, maximumAttempts: 1))
  let composite = Interceptor(interceptors: [adapter, interceptor])
  
  let session = Session(interceptor: composite)
  customSession = session
  
//  session.request(MetaService.refreshToken)
//    .validate(statusCode: 200..<300)
//    .validate(responseCode: 200..<300)
//    .responseDecodable(of: MetaModel.ResponseData<MetaModel.RefreshToken>.self) { response in
//      guard let data = try? response.result.get().data else {
//        Timber.e(response.result)
//        return
//      }
//      Timber.d("====================>\n", data)
//    }
  
  session.request(MetaService.query)
    .validate(statusCode: 200..<300)
    .validate(responseCode: 200..<300)
    .responseString(encoding: .utf8) { response in
      Timber.e("====================>\n", response.result)
    }
  
//  session.request(MetaService.ugcReleased)
//    .validate(statusCode: 200..<300)
//    .validate(responseCode: 200..<300)
//    .responseString(encoding: .utf8) { response in
//      Timber.e("====================>\n", response.result)
//    }
//  
//  session.request(MetaService.imTokenQuery)
//    .validate(statusCode: 200..<300)
//    .validate(responseCode: 200..<300)
//    .responseString(encoding: .utf8) { response in
//      Timber.e(response.result)
//    }
//  
//  session.request(MetaService.buddyList)
//    .validate(statusCode: 200..<300)
//    .validate(responseCode: 200..<300)
//    .responseString(encoding: .utf8) { response in
//      Timber.e("====================>\n", response.result)
//    }
}

/// 当发送具有非常大的主体的请求时，例如大型 JSON 对象（数百 KB 或更多，但不是已经压缩的数据，如图像），压缩传出请求主体可能有利于性能和用户的数据使用。 DeflateRequestCompressor RequestInterceptor 可用于使用 deflate Content-Encoding 执行此类压缩。
private func deflateCompressor() {
  // 向请求添加压缩器可以像任何其他 RequestInterceptor 一样完成：
  let compressor = DeflateRequestCompressor { bodyData in
    bodyData.count > 100 * 1024 // 仅当 bodyData 超过 100KB 时才进行压缩。
  }
  //  AF.request("", interceptor: .deflateCompressor)
  AF.request("", interceptor: compressor)
    .response { response in
      print(response)
    }
}



private var logger: EventMonitor {
  let logger = ClosureEventMonitor()
  logger.requestDidResume = { request in
    Timber.d("🟢: \(request)")
  }
  logger.requestDidParseResponse = { dataRequest, dateResponse in
    Timber.d("🔴: \(dateResponse)")
  }
  return logger
}

private func logging() {
  let logger = LoggerEventMonitor()
  
  let session = Session(eventMonitors: [logger])
  customSession = session
  
  session.request("https://api.metaworld.fun/tt/v2/resource/get?resourceId=100000")
    .responseString { response in
      debugPrint(#function, #line)
    }
    .responseDecodable(of: ResponseData.self) { response in
      debugPrint(#function, #line)
    }
}

private struct ResponseData: Codable {
  let code: String
  let message: String
}

/// 供 URLConvertible 和 URLRequestConvertible 协议来实现超越高级默认的 Request
///
/// 作为一个框架，Alamofire 有两个主要目标：
/// - 能够轻松实现原型和工具的网络请求
private func makeRequests() {
  // URLConvertible 鼓励符合 URLConvertible 的自定义类型，作为将特定于域的模型映射到服务器资源的便捷方法。
  let urlString = "https://httpbin.org/get"
  _ = AF.request(urlString)
  
  let url = URL(string: urlString)!
  _ = AF.request(url)
  
  let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)!
  _ = AF.request(urlComponents)
  
  // URLRequestConvertible URLRequestConvertible 作为流经请求管道的所有请求的基础。 直接使用 URLRequests 是 Alamofire 提供的 ParameterEncoders 之外自定义 URLRequest 创建的推荐方法。
  var urlRequest = URLRequest(url: URL(string: "https://httpbin.org/post")!)
  urlRequest.method = .post
  
  let parameters = ["foo": "bar"]
  do {
    urlRequest.httpBody = try JSONEncoder().encode(parameters)
  } catch {
    // Handle error.
  }
  urlRequest.headers.add(.contentType("application/json"))
  
  _ = AF.request(urlRequest).response { _ in }
  
  // Routing Requests
  _ = AF.request(Router.get(["routing": "get"])).response { _ in }
  _ = AF.request(Router.post(["routing": "post"])).response { _ in }
}

private var customManager: NetworkReachabilityManager?
private func networkReachability() {
  NetworkReachabilityManager.default?.startListening { status in
    print("Network Status Changed: \(status)")
  }
  
  // 请务必记住保留 manager，否则不会报告任何状态更改。 另外，请勿在主机字符串中包含 scheme，否则可达性将无法正常工作。
  let manager = NetworkReachabilityManager(host: "www.apple.com")
  customManager = manager
  manager?.startListening { status in
    print("Network Status Changed: \(status)")
  }
}
